#include "defines.h"
#include "lib.h"
#include "nic.h"
#include "net.h"
#include "timer.h"

int arp_request(uint8 *buffer, net_params *np)
{
    ethernet_header *ehdr = (ethernet_header *)buffer;
    arp_packet *apacket = (arp_packet *)(buffer + sizeof(*ehdr));

    memset(ehdr->eth_dst_MAC, 0xff, 6);
    memcpy(ehdr->eth_src_MAC, np->src_MAC, 6);
    ehdr->eth_ethernet_type = 0x0806;

    apacket->arp_hardware_type = 1;
    apacket->arp_protocol_type = 0x0800;
    apacket->arp_hardware_length = 6;
    apacket->arp_protocol_length = 4;
    apacket->arp_operation = 1;
    memcpy(apacket->arp_src_MAC, np->src_MAC, 6);
    memset(apacket->arp_dst_MAC, 0, 6);
    memcpy(apacket->arp_src_IP, np->src_IP, 4);
    memcpy(apacket->arp_dst_IP, np->dst_IP, 4);

    frame_send(buffer, 42);

    return 0;
}

int arp_reply(uint8 *buffer, net_params *np)
{
    // bufferにはrequest(特に相手MAC・IP)が入っていると仮定

    ethernet_header *ehdr = (ethernet_header *)buffer;
    arp_packet *apacket = (arp_packet *)(buffer + sizeof(*ehdr));

    memcpy(ehdr->eth_dst_MAC, ehdr->eth_src_MAC, 6);
    memcpy(ehdr->eth_src_MAC, np->src_MAC, 6);
    ehdr->eth_ethernet_type = 0x0806;

    apacket->arp_hardware_type = 1;
    apacket->arp_protocol_type = 0x0800;
    apacket->arp_hardware_length = 6;
    apacket->arp_protocol_length = 4;
    apacket->arp_operation = 2;
    memcpy(apacket->arp_dst_MAC, apacket->arp_src_MAC, 6);
    memcpy(apacket->arp_src_MAC, np->src_MAC, 6);
    memcpy(apacket->arp_dst_IP, apacket->arp_src_IP, 4);
    memcpy(apacket->arp_src_IP, np->src_IP, 4);

    frame_send(buffer, 42);

    return 0;
}

#define MAC_RETRY             4
#define MAC_RETRY_INTERVAL    2

int mac_get(uint8 *buffer, net_params *np)
{
    ethernet_header *ehdr = (ethernet_header *)buffer;
    arp_packet *apacket = (arp_packet *)(buffer + sizeof(*ehdr));
    int retry = 0;

    timeout_init(MAC_RETRY_INTERVAL);

    arp_request(buffer, np);
    timeout_start();

    while (1) {
        if (is_timeout()) {
            if (++retry > MAC_RETRY) break;
            arp_request(buffer, np);
            timeout_start();
        }

        if (frame_receive(buffer) == 0)
        {
            if ((ehdr->eth_ethernet_type == 0x0806)
                    && !memcmp(apacket->arp_dst_IP, np->src_IP, 4)
                    && !memcmp(apacket->arp_src_IP, np->dst_IP, 4)
                    && (apacket->arp_operation == 2))
            {
                memcpy(np->dst_MAC, apacket->arp_src_MAC, 6);
                break;

            }
        }
    }

    timeout_end();

    return (retry <= MAC_RETRY) ? 0 : -1;
}

static uint16 IP_id = 0;

int parse_IP(uint8 *buf, const char *str)
{
    uint8 a[4], b[4];
    int i, j, x;
    const char *p = str;

    for(j = 0;;) {
        for (i = 0; i < 3; i++, p++) {
            if ('0' <= *p && *p <= '9') {
                a[i] = *p;
            } else {
                break;
            }
        }
        if (i == 0) return -1;
        a[i] = '\0';

        x = atoi(a);
        if (0 <= x && x <= 0xff) {
            b[j] = (uint8)x;
        } else {
            return -1;
        }

        if (j < 3 && *p == '.') {
            j++;
            p++;
        } else if (j == 3 && *p == '\0') {
            break;
        } else {
            return -1;
        }
    }

    memcpy(buf, b, 4);
    return 0;
}

uint16 ip_checksum(uint8 *data, uint16 size)
{
    uint16 i;
    uint32 sum = 0;

    for (i = 0; i < size - 1; i += 2) {
        sum += ((uint32)data[i] << 8) + data[i+1];
        sum = (sum & 0xffff) + (sum >> 16);
    }

    if (i == size - 1) { // 奇数データ長の場合
        sum += (uint32)data[i] << 8;
        sum = (sum & 0xffff) + (sum >> 16);
    }

    return ~(uint16)sum;
}

uint16 udp_checksum(uint8 *data, uint16 size, uint8 *src_IP, uint8 *dst_IP)
{
    uint16 i;
    uint32 sum = 0;

    sum += ((uint32)src_IP[0] << 8) + src_IP[1];
    sum = (sum & 0xffff) + (sum >> 16);
    sum += ((uint32)src_IP[2] << 8) + src_IP[3];
    sum = (sum & 0xffff) + (sum >> 16);
    sum += ((uint32)dst_IP[0] << 8) + dst_IP[1];
    sum = (sum & 0xffff) + (sum >> 16);
    sum += ((uint32)dst_IP[2] << 8) + dst_IP[3];
    sum = (sum & 0xffff) + (sum >> 16);
    sum += 17;
    sum = (sum & 0xffff) + (sum >> 16);
    sum += size;
    sum = (sum & 0xffff) + (sum >> 16);

    for (i = 0; i < size - 1; i += 2) {
        sum += ((uint32)data[i] << 8) + data[i+1];
        sum = (sum & 0xffff) + (sum >> 16);
    }

    if (i == size - 1) { // 奇数データ長の場合
        sum += (uint32)data[i] << 8;
        sum = (sum & 0xffff) + (sum >> 16);
    }

    i = (uint16)sum;
    return (i == 0xffff) ? i : ~i; // i == 0はありえない
}

int send_by_udp(uint8 *buffer, uint16 size, net_params *np)
{
    ethernet_header *ehdr = (ethernet_header *)buffer;
    ip_header *ihdr = (ip_header *)(buffer + sizeof(*ehdr));
    udp_header *uhdr = (udp_header *)((uint8 *)ihdr + sizeof(*ihdr));

    uhdr->udp_src_port = np->src_port;
    uhdr->udp_dst_port = np->dst_port;
    uhdr->udp_length = sizeof(*uhdr) + size;
    uhdr->udp_checksum = 0;
    uhdr->udp_checksum = udp_checksum((uint8 *)uhdr, uhdr->udp_length, np->src_IP, np->dst_IP);

    ihdr->ip_version_length = 0x45;
    ihdr->ip_service_type = 0;
    ihdr->ip_total_length = sizeof(*ihdr) + uhdr->udp_length;
    ihdr->ip_id = IP_id++;
    ihdr->ip_flags_fragment_offset = 0x4000; // フラグメント禁止
    ihdr->ip_time_to_live = 128;
    ihdr->ip_protocol = 17;
    memcpy(ihdr->ip_src_IP, np->src_IP, 4);
    memcpy(ihdr->ip_dst_IP, np->dst_IP, 4);
    ihdr->ip_checksum = 0;
    ihdr->ip_checksum = ip_checksum((uint8 *)ihdr, sizeof(*ihdr));

    memcpy(ehdr->eth_dst_MAC, np->dst_MAC, 6);
    memcpy(ehdr->eth_src_MAC, np->src_MAC, 6);
    ehdr->eth_ethernet_type = 0x0800;
    frame_send(buffer, ihdr->ip_total_length + sizeof(*ehdr));

    return 0;
}
